#!/usr/bin/ruby

####################################
#        MFqdisc.rb                                                                                                     
#        description;                                                                                                    
#            provide qdisc control functions for MFctrl                                                  
#        version           1.1                                                                                          
#        last modified     08/02/05                                                                             
#       Last change, updated error reporting
####################################

QD_VERSION=1.2
def qdisc_ver
   return "\tQDISC library version: #{QD_VERSION}\n"
end

def fmatch(input)
    instring=input.gsub(/ /,'')
    if instring.include?("src=")
        t=instring.split(/=/)
        v=t[1].gsub(/ /,'')
        return["protocol ip","u32 match ip src #{v}"]
    elsif instring.include?("dst=")
        t=instring.split(/=/)
        v=t[1].gsub(/ /,'')
        return["protocol ip","u32 match ip dst #{v}"]
    elsif instring.include?("sport=")
        t=instring.split(/=/)
        v=t[1].gsub(/ /,'')
        return["protocol ip","u32 match ip sport #{v} 0xFFFF"]
    elsif instring.include?("dport=")
        t=instring.split(/=/)
        v=t[1].gsub(/ /,'')
        return["protocol ip","u32 match ip dport #{v} 0xFFFF"]
    elsif instring.include?("protocol=")
        t=instring.split(/=/)
        v=t[1].gsub(/ /,'')
        return["protocol ip","u32 match ip protocol #{v} 0xFFFF"]
    elsif instring.include?("tos=")
        t=instring.split(/=/)
        v=t[1].gsub(/ /,'')
        tos=v
        return["protocol ip", "u32 match ip tos #{tos} 0xFF"]
    else
        return nil
    end
end

def parse_filter(fstring)
    return nil if !fstring   
    retary=Array.new
    fstring.each {|f|
        mystring=f.downcase
        if mystring.include?(" and ")
            # we have a mutli match
            tmp=mystring.split(" and ")
            tmp.each{|t|
                retary << fmatch(t)
            }
	else
            retary << fmatch(mystring)
        end
    }
    return retary
end
def start_flow(dir,flow,interface,count,leaf)
    return nil if flow.include?("\.")
    # set defaults
    fallot=1514
    fbuffer=10240
    fbandwidth="100Mbit"
    frate=fbandwidth
    fprio=1
    fbounded="bounded"
    flatency="300ms"
    fburst=1500
    flimit=5120
    fmtu=11500
    if test(?r,"#{dir}/#{flow}/allot")
        tmp=MFreadlines("#{dir}/#{flow}/allot")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                fallot=t.to_i
            end
        end
    end
    if test(?r,"#{dir}/#{flow}/bandwidth")
        tmp=MFreadlines("#{dir}/#{flow}/bandwidth")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                fbandwidth=t
            end
        end
    end
    if test(?r,"#{dir}/#{flow}/buffer")
        tmp=MFreadlines("#{dir}/#{flow}/buffer")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                fbuffer=t.to_i
            end
        end
    end
    if test(?r,"#{dir}/#{flow}/burst")
        tmp=MFreadlines("#{dir}/#{flow}/burst")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                fburst=t.to_i
            end
        end
    end
    if test(?r,"#{dir}/#{flow}/latency")
        tmp=MFreadlines("#{dir}/#{flow}/latency")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                flatency=t
            end
        end
    end
    if test(?r,"#{dir}/#{flow}/limit")
        tmp=MFreadlines("#{dir}/#{flow}/limit")
        if tmp[0]
           t=tmp.shift
           if t.index(/[0-9]/)
               flimit=t.to_i
           end
        end
    end
    if test(?r,"#{dir}/#{flow}/mtu")
        tmp=MFreadlines("#{dir}/#{flow}/mtu")
        if  tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                fmtu=t.to_i
            end
        end
    end
    if test(?r,"#{dir}/#{flow}/priority")
        tmp=MFreadlines("#{dir}/#{flow}/priority")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                fprio=t.to_i
            end
        end
    end
    if test(?r,"#{dir}/#{flow}/rate")
        tmp=MFreadlines("#{dir}/#{flow}/rate")
        if  tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                frate=t
            end
        end
    end
    if test(?r,"#{dir}/#{flow}/bounded")
        tmp=MFreadlines("#{dir}/#{flow}/bounded")
        if tmp[0]
            bnd=tmp.shift.downcase
            if bnd == "yes" || bnd == "bounded"
                fbounded="bounded"
            else
                fbounded=" "
            end
        end
    end        
    # add our flow
    if !system("#{$tc} class add dev #{interface} parent 1:0 classid 1:#{count} cbq bandwidth #{fbandwidth} rate #{frate} prio #{fprio} allot #{fallot} #{fbounded}")
        return "\tFAILED: addid class to #{interface} on queue #{flow}\n"
    end
    if !system("#{$tc} qdisc add dev #{interface} parent 1:#{count} handle #{leaf}: sfq")
    print ("#{$tc} qdisc add dev #{interface} parent 1:#{count} handle #{leaf}: tbf rate #{frate} buffer #{fbuffer} limit #{flimit} mtu #{fmtu}")
        return "\tFAILED: adding leaf to #{interface} on queue #{flow}\n"
    end
    myfilter=parse_filter(MFreadlines("#{dir}/#{flow}/filters"))
    retary=Array.new
    return nil if ! myfilter
    myfilter.each{|f|
        if !system("#{$tc} filter add dev #{interface} parent 1:0 #{f[0]} prio #{fprio} #{f[1]} flowid 1:#{count}")
            retary<< "\tFAILED: adding filter to #{interface} on queue #{flow}\n"
        end
    }
    return retary
end

def qstart(zone)
    if !active?("/jail/acl/zones/#{zone}")
        return "\tZONE #{zone} is deactivated by config.. skipping\n"
    end
    if !active?("/jail/acl/zones/#{zone}/qdisc")
        return "\tZONE #{zone} qdisc is deactivated by config... skipping\n"
    end
    retary=Array.new
    if !test(?r, "/jail/acl/zones/#{zone}/interface/int")
        return "\tFAILED: Interface configuration for zone #{zone} is invalid\n"
    end
    interface=MFreadlines("/jail/acl/zones/#{zone}/interface/int").shift

    # set defaults
    rallot=1514
    ravpkt=1000
    rbandwidth="100Mbit"
    rrate=rbandwidth
    rprio=5
    rbounded="bounded"
    rcell=8
    rmaxburst=20
    if test(?r,"/jail/acl/zones/#{zone}/qdisc/allot")
        tmp=MFreadlines("/jail/acl/zones/#{zone}/qdisc/allot")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                rallot=t.to_i
            end
        end
    end
    if test(?r,"/jail/acl/zones/#{zone}/qdisc/avpkt")
        tmp=MFreadlines("/jail/acl/zones/#{zone}/qdisc/avpkt")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                ravpkt=t.to_i
            end
        end
    end        
    if test(?r,"/jail/acl/zones/#{zone}/qdisc/bandwidth")
        tmp=MFreadlines("/jail/acl/zones/#{zone}/qdisc/bandwidth")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                rbandwidth=t
            end
        end
    end        
    if test(?r,"/jail/acl/zones/#{zone}/qdisc/rate")
        tmp=MFreadlines("/jail/acl/zones/#{zone}/qdisc/rate")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                rrate=t
            end
        end
    end    
    if test(?r,"/jail/acl/zones/#{zone}/qdisc/prio")
        tmp=MFreadlines("/jail/acl/zones/#{zone}/qdisc/prio")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                rprio=t.to_i
            end
        end
    end    
    if test(?r,"/jail/acl/zones/#{zone}/qdisc/bounded")
        tmp=MFreadlines("/jail/acl/zones/#{zone}/qdisc/bounded")
        if tmp[0]
            bnd=tmp.shift.downcase
            if bnd =="yes" || bnd =="bounded"
                rbounded="bounded"
            else
                rbounded=" "
            end
        end
    end        
    if test(?r,"/jail/acl/zones/#{zone}/qdisc/cell")
        tmp=MFreadlines("/jail/acl/zones/#{zone}/qdisc/cell")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                rcell=t.to_i
            end
        end
    end        
    if test(?r,"/jail/acl/zones/#{zone}/qdisc/maxburst")
        tmp=MFreadlines("/jail/acl/zones/#{zone}/qdisc/maxburst")
        if tmp[0]
            t=tmp.shift
            if t.index(/[0-9]/)
                rmaxburst=t.to_i
            end
        end
    end        
    if !system("#{$tc} qdisc add dev #{interface} root handle 1:0 cbq bandwidth #{rbandwidth} avpkt #{ravpkt} cell #{rcell}")
        return "\tFAILED: adding root qdisc to dev #{interface} for zone #{zone}\n"
    end

    if test(?d,"/jail/acl/zones/#{zone}/qdisc/queue")
        dir=Dir.new("/jail/acl/zones/#{zone}/qdisc/queue")
        count=1
        leaf=10
            dir.each {|q|
                #if !system("#{$tc} class add dev #{interface} parent 1:0 classid 1:#{count} cbq bandwidth #{rbandwidth} rate #{rrate} prio #{rprio} allot #{rallot} cell #{rcell} maxburst #{rmaxburst} avpkt #{ravpkt} #{rbounded}")
                #    return "\tFAILED: adding root class to dev #{interface} for zone #{zone}\n"
		#end
                retary << start_flow("/jail/acl/zones/#{zone}/qdisc/queue",q,interface,count,leaf)
                count +=1
                leaf += 10
            }
    end
    return  retary
end

def qdisc_stop(zone)
    if zone.upcase == "ALL"
        dir=Dir.new("/jail/acl/zones/")
        dir.each {|zone|
            next if zone.include?("\.")
            next if zone.upcase == "FORWARD"
            next if ! test(?r, "/jail/acl/zones/#{zone}/interface")
            next if ! test(?r, "/jail/acl/zones/#{zone}/qdisc")
            next if ! test(?r, "/jail/acl/zones/#{zone}/interface/int")
            next if ! test(?r, "/jail/acl/zones/#{zone}/interface")
            next if ! active?("/jail/acl/zones/#{zone}/qdisc")
            interface=MFreadlines("/jail/acl/zones/#{zone}/interface/int").shift
            if !system("#{$tc} qdisc delete dev #{interface} root")
                return "\tFAILED: removing qdisc from zone #{zone}\n"
            end
        }
    else      
        if ! test(?r, "/jail/acl/zones/#{zone}/interface")
            return "\tFAILED: Interface invalid for zone #{zone}\n"
        end
        if !test(?r, "/jail/acl/zones/#{zone}/interface/int")
            return "\tFAILED: Interface description for zone #{zone} not found\n"
        end
        if ! active?("/jail/acl/zones/#{zone}/qdisc")
            return "\tNOTICE: qdisc is marked as inactive in configuration for this zone... skipping\n"
        end
        interface=MFreadlines("/jail/acl/zones/#{zone}/interface/int").shift
        if !system("#{$tc} qdisc delete dev #{interface} root")
            return "\tFAILED: removing qdisc from zone #{zone}\n"
        end
    end
    return nil
end

def qdisc_start(zone)
    retary = Array.new
    if zone.upcase == "ALL"
        dir=Dir.new("/jail/acl/zones/")
        dir.each {|myzone|
            next if myzone.include?("\.")
            next if myzone.upcase == "FORWARD"
            next if ! test(?r, "/jail/acl/zones/#{myzone}/interface")
            next if ! test(?r, "/jail/acl/zones/#{myzone}/qdisc")
            next if ! test(?r, "/jail/acl/zones/#{myzone}/interface/int")
            retary << qstart(myzone)
        }
    else
        return qstart(zone)
    end
    return retary
end

def qdisc_restart(zone)
    retary=Array.new
    retary << qdisc_stop(zone)
    retary << qdisc_start(zone)
    return retary
end
